package com.ahdms.es.util;

import com.ahdms.es.gm.constant.IKIObjectIdentifiers;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;

public class CertTools {

    /**
     * 获取ASN1OctetString扩展项值
     *
     * @param certificate
     * @param identifier
     * @return
     */
    public static String parseExtensionValue(Certificate certificate, ASN1ObjectIdentifier identifier) {
        return Optional.ofNullable(parseExtensionValueByte(certificate, identifier))
                .map(String::new)
                .orElse(null);
    }

    /**
     * 获取ASN1OctetString扩展项值
     *
     * @param extensions
     * @param identifier
     * @return
     */
    public static String parseExtensionValue(Extensions extensions, ASN1ObjectIdentifier identifier) {
        return Optional.ofNullable(parseExtensionValueByte(extensions, identifier))
                .map(String::new)
                .orElse(null);
    }

    /**
     * 获取ASN1OctetString扩展项值
     *
     * @param extensions
     * @param identifier
     * @return
     */
    public static byte[] parseExtensionValueByte(Extensions extensions, ASN1ObjectIdentifier identifier) {
        return Optional.ofNullable(extensions)
                .map(exts -> exts.getExtension(identifier))
                .map(Extension::getExtnValue)
                .map(ASN1OctetString::getOctets)
                .orElse(null);
    }

    /**
     * 获取ASN1OctetString扩展项值
     *
     * @param certificate
     * @param identifier
     * @return
     */
    public static byte[] parseExtensionValueByte(Certificate certificate, ASN1ObjectIdentifier identifier) {
        return Optional.ofNullable(certificate)
                .map(Certificate::getTBSCertificate)
                .map(TBSCertificate::getExtensions)
                .map(exts -> parseExtensionValueByte(exts, identifier))
                .orElse(null);
    }

    /**
     * 获取主体/使用者密钥标识符
     *
     * @param extensions
     * @return
     */
    public static String getSubjectKeyIdentifier(Extensions extensions) {
        SubjectKeyIdentifier identifier = SubjectKeyIdentifier.fromExtensions(extensions);
        if (identifier == null) {
            return null;
        }
        return ConvertorUtils.getHexString(identifier.getKeyIdentifier(), false);
    }

    /**
     * 获取颁发者密钥标识符
     *
     * @param extensions
     * @return
     */
    public static String getAuthorityKeyIdentifier(Extensions extensions) {
        AuthorityKeyIdentifier identifier = AuthorityKeyIdentifier.fromExtensions(extensions);
        if (identifier == null) {
            return null;
        }
        return ConvertorUtils.getHexString(identifier.getKeyIdentifier(), false);
    }

    /**
     * 获取LDAP或IRL地址
     *
     * @param extensions
     * @param identifier
     * @return
     */
    public static String getLdaporIrlPoint(Extensions extensions, ASN1ObjectIdentifier identifier) {
        String ret = null;
        Extension ext = extensions.getExtension(identifier);
        if (ext == null) {
            return null;
        }
        try {
            ASN1InputStream dIn = new ASN1InputStream(ext.getExtnValue().getOctetStream());
            ASN1Sequence seq = (ASN1Sequence) dIn.readObject();
            int dpCount = seq.size();
            for (int i = 0; i < dpCount; i++) {
                // 第一个分布点(DistributionPoint)
                ASN1Sequence point1 = (ASN1Sequence) seq.getObjectAt(i);
                ASN1Object tobj = (ASN1Object) point1.getObjectAt(0);
                while (tobj instanceof DERTaggedObject && !((DERTaggedObject) tobj).isEmpty()) {
                    if (tobj instanceof DERTaggedObject) {
                        tobj = ((DERTaggedObject) tobj).getObject();
                    }
                }
                DEROctetString os = (DEROctetString) tobj.toASN1Primitive();
                ret = new String(os.getOctets());
            }

            dIn.close();
        } catch (Exception e) {
        }
        return ret;
    }

    /**
     * 获取密钥信息访问字符串
     *
     * @param extensions
     * @return
     */
    public static String getAuthorityInformationAccess(Extensions extensions) {
        AuthorityInformationAccess access = AuthorityInformationAccess.fromExtensions(extensions);
        if (access == null) {
            return null;
        }
        AccessDescription[] descriptions = access.getAccessDescriptions();
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < descriptions.length; i++) {
            stringBuffer.append("[" + (i + 1) + "]" + descriptions[i].getAccessLocation().getName());
        }
        return stringBuffer.toString();
    }

    /**
     * 获取基本限制
     *
     * @param extensions
     * @return
     */
    public static boolean getBasicConstraints(Extensions extensions) {
        BasicConstraints basic = BasicConstraints.fromExtensions(extensions);
        if (basic == null) {
            return basic.isCA();
        }
        return false;
    }


    /**
     * 获取扩展密钥用法
     *
     * @param extensions
     * @return
     */
    public static String getExtendedKeyUsage(Extensions extensions) {
        ExtendedKeyUsage usage = ExtendedKeyUsage.fromExtensions(extensions);
        if (usage == null) {
            return null;
        }
        StringBuffer stringBuffer = new StringBuffer();
        KeyPurposeId[] purposeIds = usage.getUsages();
        //TODO 暂未进行过扩展密钥用法解析测试
        for (KeyPurposeId purposeId : purposeIds) {
            if (usage.hasKeyPurposeId(purposeId)) {
                stringBuffer.append(purposeId.toString() + ",");
            }
        }
        int maxLeng = stringBuffer.length();
        if (maxLeng > 2) {
            stringBuffer.delete(maxLeng - 1, maxLeng);
            return stringBuffer.toString();
        }
        return null;
    }


    /**
     * 获取密钥用法字符串
     *
     * @param extensions
     * @return
     */
    public static String getKeyUsage(Extensions extensions) {
        KeyUsage keyUsage = KeyUsage.fromExtensions(extensions);
        if (keyUsage == null) {
            return null;
        }
        StringBuffer stringBuffer = new StringBuffer();
        if (keyUsage.hasUsages(KeyUsage.digitalSignature)) {
            stringBuffer.append("digitalSignature,");
        }
        if (keyUsage.hasUsages(KeyUsage.nonRepudiation)) {
            stringBuffer.append("nonRepudiation,");
        }
        if (keyUsage.hasUsages(KeyUsage.keyEncipherment)) {
            stringBuffer.append("keyEncipherment,");
        }
        if (keyUsage.hasUsages(KeyUsage.dataEncipherment)) {
            stringBuffer.append("dataEncipherment,");
        }
        if (keyUsage.hasUsages(KeyUsage.keyAgreement)) {
            stringBuffer.append("keyAgreement,");
        }
        if (keyUsage.hasUsages(KeyUsage.keyCertSign)) {
            stringBuffer.append("keyCertSign,");
        }
        if (keyUsage.hasUsages(KeyUsage.cRLSign)) {
            stringBuffer.append("cRLSign,");
        }
        if (keyUsage.hasUsages(KeyUsage.encipherOnly)) {
            stringBuffer.append("encipherOnly,");
        }
        if (keyUsage.hasUsages(KeyUsage.decipherOnly)) {
            stringBuffer.append("decipherOnly,");
        }

        int maxLeng = stringBuffer.length();
        if (maxLeng > 7) {
            stringBuffer.delete(maxLeng - 1, maxLeng);
            return stringBuffer.toString();
        }
        return null;

    }


    /**
     * 获取版本号
     *
     * @param certificate
     * @return
     */
    public static String parseVersion(Certificate certificate) {
        return Optional.ofNullable(parseExtensionValue(certificate, IKIObjectIdentifiers.uid_identity_version))
                .orElseGet(() -> getVersionString(certificate));
    }

    /**
     * 获取Identity
     *
     * @param extensions
     * @return
     */
    public static String parseIdentity(Extensions extensions) {
        return parseExtensionValue(extensions, IKIObjectIdentifiers.uid_identity_value);
    }


    private static String getVersionString(Certificate certificate) {
        return "V" + certificate.getVersionNumber();
    }

    /**
     * 加密秘钥比对
     *
     * @param key1
     * @param key2
     * @return
     */
    public static boolean encKeyEqual(byte[] key1, byte[] key2) {
        byte[] x1 = new byte[32];
        byte[] x2 = new byte[32];
        byte[] y1 = new byte[32];
        byte[] y2 = new byte[32];
        System.arraycopy(key1, 1, x1, 0, 32);
        System.arraycopy(key1, 33, y1, 0, 32);
        System.arraycopy(key2, 1, x2, 0, 32);
        System.arraycopy(key2, 33, y2, 0, 32);

        return Arrays.equals(x1, x2) && Arrays.equals(y1, y2);
    }

    public static boolean isExpired(Certificate certificate) {
        return certificate.getEndDate().getDate().before(new Date());
    }

    /**
     * 根据证书获取公钥，signFlag-true代表签名公钥，signFlag-false代表加密公钥
     *
     * @param cert
     * @param isSign 是否签名公钥
     * @return byte[]
     */
    private static byte[] getPublicKeyBytes(Certificate cert, boolean isSign) {
        byte[] publicKey;
        byte[] origPubKeyBytes = cert.getSubjectPublicKeyInfo().getPublicKeyData().getOctets();
        ASN1ObjectIdentifier algOid = cert.getSubjectPublicKeyInfo().getAlgorithm().getAlgorithm();
        ASN1ObjectIdentifier paramOid = ASN1ObjectIdentifier.getInstance(cert.getSubjectPublicKeyInfo().getAlgorithm().getParameters());
        if (IKIObjectIdentifiers.ecc_pub_key.getId().equals(algOid.getId()) &&
                IKIObjectIdentifiers.sm_puk_combined.getId().equals(paramOid.getId())) {
            if (isSign) {
                // 签名公钥
                publicKey = new byte[origPubKeyBytes.length / 2];
                System.arraycopy(origPubKeyBytes, origPubKeyBytes.length / 2, publicKey, 0, publicKey.length);
            } else {
                // 加密公钥
                publicKey = new byte[origPubKeyBytes.length / 2];
                System.arraycopy(origPubKeyBytes, 0, publicKey, 0, publicKey.length);
            }
        } else {
            publicKey = origPubKeyBytes;
        }
        return publicKey;
    }

    @SuppressWarnings("unchecked")
    public static ASN1Encodable getSubjectDirectoryAttributesAttr(Extensions exts, ASN1ObjectIdentifier sdaAttrOid) {
        Extension sdaExt = exts.getExtension(Extension.subjectDirectoryAttributes);
        if (sdaExt == null) {
            return null;
        }

        ASN1Encodable ret = null;
        SubjectDirectoryAttributes subDirAttrs = SubjectDirectoryAttributes.getInstance(sdaExt.getExtnValue().getOctets());
        Vector<Attribute> attrs = subDirAttrs.getAttributes();
        Iterator<Attribute> ite = attrs.iterator();
        while (ite.hasNext()) {
            Attribute attr = ite.next();
            if (sdaAttrOid.getId().equals(attr.getAttrType().getId())) {
                ret = attr.getAttrValues().getObjectAt(0);
                break;
            }
        }

        return ret;
    }


    /**
     * 从byte证书获取为证书序列号返回BigInteger字符串
     *
     * @param signCertByte
     * @return String
     * @throws IOException
     */
    public static String getSn(byte[] signCertByte) {
        Certificate cert = CertTools.convertToCert(signCertByte);
        return getSn(cert);
    }

    /**
     * 从byte证书获取为证书序列号返回BigInteger字符串
     *
     * @param cert
     * @return String
     * @throws IOException
     */
    public static String getSn(Certificate cert) {

        // 获取Certificate中的标识
        return cert.getSerialNumber().toString();
    }

    /**
     * 将StringCert转换为ASN1Certificate
     *
     * @param certStr
     * @return Certificate
     * @throws IOException
     */
    public static Certificate convertToCert(String certStr) {
        return convertToCert(Base64.decode(certStr));
    }

    /**
     * 将byte转换为ASN1Certificate证书
     *
     * @param certBytes
     * @return Certificate
     * @throws IOException
     */
    public static Certificate convertToCert(byte[] certBytes) {
        return Certificate.getInstance(ASN1Sequence.getInstance(certBytes));
    }

    /**
     * 通过证书获取16进制字符串序列号
     *
     * @param cert
     * @return String
     */
    public static String getSnX16(Certificate cert) {
        return "00" + new BigInteger(cert.getSerialNumber().toString(), 10).toString(16);
    }

    /**
     * 通过证书获取DN
     *
     * @param cert
     * @return String
     */
    public static String getDn(Certificate cert) {
        return cert.getSubject().toString();
    }

    /**
     * 通过证书获取颁发者
     *
     * @param cert
     * @return String
     */
    public static String getIssuer(Certificate cert) {
        return cert.getIssuer().toString();
    }


    /**
     * 取crl分布点
     *
     * @param cert
     * @return String
     */
    public static String getIRLPoint(Certificate cert) {
        String ret = null;
        Extensions extensions = cert.getTBSCertificate().getExtensions();
        Extension ext = extensions.getExtension(Extension.cRLDistributionPoints);
        if (ext != null) {
            try {
                ASN1InputStream dIn = new ASN1InputStream(ext.getExtnValue().getOctetStream());
                ASN1Sequence seq = (ASN1Sequence) dIn.readObject();
                int dpCount = seq.size();
                for (int i = 0; i < dpCount; i++) {
                    // 第一个分布点(DistributionPoint)
                    ASN1Sequence point1 = (ASN1Sequence) seq.getObjectAt(i);
                    ASN1Object tobj = (ASN1Object) point1.getObjectAt(0);
                    while (tobj instanceof DERTaggedObject && !((DERTaggedObject) tobj).isEmpty()) {
                        if (tobj instanceof DERTaggedObject) {
                            tobj = ((DERTaggedObject) tobj).getObject();
                        }
                    }
                    DEROctetString os = (DEROctetString) tobj.toASN1Primitive();
                    ret = new String(os.getOctets());
                }

                dIn.close();
            } catch (Exception e) {
//                log.error("crl分布点处理出错了!", e);
            }
        }
        return ret;
    }

    /**
     * 从证书获取签名公钥
     *
     * @param certByte
     * @return byte[]
     * @throws IOException
     */
    public static byte[] getSignPublicKey(byte[] certByte) {
        Certificate cert = convertToCert(certByte);
        return getSignPublicKey(cert);
    }

    /**
     * 从证书获取加密公钥
     *
     * @param certByte
     * @return byte[]
     * @throws IOException
     */
    public static byte[] getEncPublicKey(byte[] certByte) {
        Certificate cert = convertToCert(certByte);
        return getEncPublicKey(cert);
    }


    /**
     * 从证书获取签名公钥
     *
     * @param cert
     * @return byte[]
     * @throws IOException
     */
    public static byte[] getSignPublicKey(Certificate cert) {
        return getPublicKeyBytes(cert, true);
    }

    /**
     * 从证书获取加密公钥
     *
     * @param cert
     * @return byte[]
     * @throws IOException
     */
    public static byte[] getEncPublicKey(Certificate cert) {
        return getPublicKeyBytes(cert, false);
    }


    /**
     * 将证书转换为X509证书
     *
     * @param cert
     * @return byte[]
     * @throws IOException
     */
    public static X509Certificate getX509Certificate(Certificate cert) {
        CertificateFactory certificateFactory = null;
        try {
            certificateFactory = CertificateFactory.getInstance("X509", new BouncyCastleProvider());
        } catch (CertificateException e) {
            return null;
        }
        X509Certificate x509Cert = null;
        try {
            x509Cert = (X509Certificate) certificateFactory
                    .generateCertificate(new ByteArrayInputStream(cert.getEncoded()));
        } catch (CertificateException e) {
            return null;
        } catch (IOException e) {
            return null;
        }
        return x509Cert;
    }

}
